// Copyright 2003-2005 Arthur van Hoff, Rick Blair
// Licensed under Apache License version 2.0
// Original license LGPL

package javax.jmdns.impl.tasks;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

import javax.jmdns.impl.DNSConstants;
import javax.jmdns.impl.DNSOutgoing;
import javax.jmdns.impl.DNSState;
import javax.jmdns.impl.JmDNSImpl;
import javax.jmdns.impl.ServiceInfoImpl;

/**
 * The Renewer is there to send renewal announcment when the record expire for
 * ours infos.
 */
public class Renewer extends TimerTask {

   /**
     * 
     */
   private final JmDNSImpl jmDNSImpl;
   /**
    * The state of the announcer.
    */
   DNSState taskState = DNSState.ANNOUNCED;

   public Renewer(JmDNSImpl jmDNSImpl) {
      this.jmDNSImpl = jmDNSImpl;
      // Associate host to this, if it needs renewal
      if (this.jmDNSImpl.getState() == DNSState.ANNOUNCED) {
         this.jmDNSImpl.setTask(this);
      }
      // Associate services to this, if they need renewal
      synchronized (this.jmDNSImpl) {
         for (Iterator s = this.jmDNSImpl.getServices().values().iterator(); s.hasNext();) {
            ServiceInfoImpl info = (ServiceInfoImpl) s.next();
            if (info.getState() == DNSState.ANNOUNCED) {
               info.setTask(this);
            }
         }
      }
   }

   public void start(Timer timer) {
      timer.schedule(this, DNSConstants.ANNOUNCED_RENEWAL_TTL_INTERVAL, DNSConstants.ANNOUNCED_RENEWAL_TTL_INTERVAL);
   }

   public boolean cancel() {
      // Remove association from host to this
      if (this.jmDNSImpl.getTask() == this) {
         this.jmDNSImpl.setTask(null);
      }

      // Remove associations from services to this
      synchronized (this.jmDNSImpl) {
         for (Iterator i = this.jmDNSImpl.getServices().values().iterator(); i.hasNext();) {
            ServiceInfoImpl info = (ServiceInfoImpl) i.next();
            if (info.getTask() == this) {
               info.setTask(null);
            }
         }
      }

      return super.cancel();
   }

   public void run() {
      DNSOutgoing out = null;
      try {
         // send probes for JmDNS itself
         if (this.jmDNSImpl.getState() == taskState) {
            if (out == null) {
               out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA);
            }
            this.jmDNSImpl.getLocalHost().addAddressRecords(out, false);
            this.jmDNSImpl.advanceState();
         }
         // send announces for services
         // Defensively copy the services into a local list,
         // to prevent race conditions with methods registerService
         // and unregisterService.
         List list;
         synchronized (this.jmDNSImpl) {
            list = new ArrayList(this.jmDNSImpl.getServices().values());
         }
         for (Iterator i = list.iterator(); i.hasNext();) {
            ServiceInfoImpl info = (ServiceInfoImpl) i.next();
            synchronized (info) {
               if (info.getState() == taskState && info.getTask() == this) {
                  info.advanceState();
                  if (out == null) {
                     out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA);
                  }
                  info.addAnswers(out, DNSConstants.DNS_TTL, this.jmDNSImpl.getLocalHost());
               }
            }
         }
         if (out != null) {
            this.jmDNSImpl.send(out);
         } else {
            // If we have nothing to send, another timer taskState ahead
            // of us has done the job for us. We can cancel.
            cancel();
         }
      } catch (Throwable e) {
         this.jmDNSImpl.recover();
      }

      taskState = taskState.advance();
      if (!taskState.isAnnounced()) {
         cancel();

      }
   }
}